Modelos Clássicos¶
Bibliotecas Necessárias
import random
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import lightgbm as lgb
from xgboost import XGBRegressor
from sklearn.ensemble import AdaBoostRegressor
from sklearn.preprocessing import MinMaxScaler, StandardScaler
from sklearn.metrics import mean_absolute_error, mean_squared_error
Ler e separar os dados
file_path = 'Data_noNorm.csv'
data = pd.read_csv(file_path)
# Garantir que a coluna 'Date' está no formato correto e sem o fuso horário
data['Date'] = pd.to_datetime(data['Date']) # Converter para datetime
data.set_index('Date', inplace=True) # Definir 'Date' como índice
# Separar os conjuntos de treino, validação e teste
train_data = data.loc[:'2022-12-31']
val_data = data.loc['2023-01-01':'2023-12-31']
test_data = data.loc['2024-01-01':'2024-01-31']
# Separar features (X) e target (y)
X_train = train_data.drop(columns=['Future_Return'])
y_train = train_data['Future_Return']
X_val = val_data.drop(columns=['Future_Return'])
y_val = val_data['Future_Return']
X_test = test_data.drop(columns=['Future_Return'])
y_test = test_data['Future_Return']
Escolha das melhores features, com recurso ao modelo LightGBM
Optamos por escolher o LightGBM, por ser um modelo com elevada capacidade em lidar com um grande volume de dados, com padrões complexos.
Além disso, apresenta uma boa eficiência computacional, sem introduzir ruído.
Referência: Ke, Guolin, et al. "LightGBM: A highly efficient gradient boosting decision tree." Advances in Neural Information Processing Systems. 2017
lgb_model = lgb.LGBMRegressor(
n_estimators=3500,
max_depth=10,
learning_rate=0.001,
n_jobs=-1,
random_state=42
)
lgb_model.fit(X_train, y_train)
feature_importances = lgb_model.feature_importances_
[LightGBM] [Warning] Found whitespace in feature_names, replace with underlines [LightGBM] [Info] Auto-choosing row-wise multi-threading, the overhead of testing was 0.019419 seconds. You can set `force_row_wise=true` to remove the overhead. And if memory is not enough, you can set `force_col_wise=true`. [LightGBM] [Info] Total Bins 4906 [LightGBM] [Info] Number of data points in the train set: 1516448, number of used features: 23 [LightGBM] [Info] Start training from score 0.000723
Plot das melhores features
plt.figure(figsize=(10, 6))
plt.barh(X_train.columns, feature_importances)
plt.ylabel('Índice das Features')
plt.xlabel('Importância')
plt.title('Importância das Features')
plt.legend()
plt.show()
No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
feature_importances
array([ 28, 172, 16, 7, 15, 1080, 352, 0, 0,
46, 7191, 0, 26, 72, 951, 6, 0, 19254,
3248, 23167, 18836, 22503, 8030])
Escolhemos as features com importancia superior à mediana das feature_importances, uma vez que a mediana não é influenciada por valores extremamente altos ou baixos, ao contrário da média.
selected_features = X_train.columns[feature_importances > np.median(feature_importances)]
print(f"Features selecionadas: {list(selected_features)}")
# Reduzir os datasets às features selecionadas
X_train_selected = X_train[selected_features]
X_val_selected = X_val[selected_features]
X_test_selected = X_test[selected_features]
Features selecionadas: ['Close', 'Volume', 'Ticker', 'RSI', 'Momentum_10', 'Daily_Return', 'Rolling_Volatility', 'Year', 'Month', 'Day', 'Weekday']
1. Normalização dos dados usando o Min-Max
Ideal para colunas com valores que possuem limites conhecidos ou métricas específicas que variam dentro de uma faixa pré-definida.
Neste caso, podemos aplicar o Min-Max a indicadores técnicos como RSI e Momentum.
2. StandardScaler
Ideal para variáveis cujas escalas não são limitadas, especialmente aquelas diretamente relacionadas a preços, volumes e volatilidades.
Assim, podemos aplicar o StandardScaler às seguintes features:
Preços e volumes:
- Close;
- Volume;
- Daily_Return.
Indicadores de risco e volatilidade:
- Rolling_Volatility.
Este documento fornece suporte empírico para a utilização da normalização do Z-score e do Min-Max Scaling em tarefas de previsão de ações \
Referência: "Forecasting Daily Stock Movement Using a Hybrid Normalization Based Intersection Feature Selection and ANN" by Kumari Binita and Swarnkar Tripti (2023)
min_max_scaler = MinMaxScaler()
standard_scaler = StandardScaler()
cols_to_normalize_minmax = [col for col in selected_features if col in ['RSI', 'Momentum_10']]
cols_to_normalize_standard = [col for col in selected_features if col in ['Close', 'Volume', 'Daily_Return', 'Rolling_Volatility']]
# Criar cópias para evitar modificar os dados originais
X_train_normalized = X_train_selected.copy()
X_val_normalized = X_val_selected.copy()
X_test_normalized = X_test_selected.copy()
# Aplicar normalização Min-Max nas colunas definidas
X_train_normalized[cols_to_normalize_minmax] = min_max_scaler.fit_transform(X_train_selected[cols_to_normalize_minmax])
X_val_normalized[cols_to_normalize_minmax] = min_max_scaler.transform(X_val_selected[cols_to_normalize_minmax])
X_test_normalized[cols_to_normalize_minmax] = min_max_scaler.transform(X_test_selected[cols_to_normalize_minmax])
# Aplicar normalização StandardScaler nas colunas definidas
X_train_normalized[cols_to_normalize_standard] = standard_scaler.fit_transform(X_train_selected[cols_to_normalize_standard])
X_val_normalized[cols_to_normalize_standard] = standard_scaler.transform(X_val_selected[cols_to_normalize_standard])
X_test_normalized[cols_to_normalize_standard] = standard_scaler.transform(X_test_selected[cols_to_normalize_standard])
Função para calcular R^2, a partir do coeficiente de pearson
def r2(y_real, y_pred):
# Calcular as médias
mean_y_real = np.mean(y_real)
mean_y_pred = np.mean(y_pred)
# Numerador: Covariância entre y_real e y_pred
covariance = np.sum((y_real - mean_y_real) * (y_pred - mean_y_pred))
# Denominador: Produto dos desvios padrão de y_real e y_pred
std_y_real = np.sqrt(np.sum((y_real - mean_y_real) ** 2))
std_y_pred = np.sqrt(np.sum((y_pred - mean_y_pred) ** 2))
# Coeficiente de correlação de Pearson (r)
pearson_r = covariance / (std_y_real * std_y_pred)
r2_pearson = pearson_r ** 2
return r2_pearson
Decidimos implementar 3 modelos diferentes de Machine Learning, o AdaBoost, XGBoost e o LightGBM, para prever os retornos diários de cada ação.
AdaBoost¶
Primeiro, recorremos ao Adaboost por ser um modelo robusto em lidar com dados de alta dimensionalidade, além de ter uma boa capacidade para capturar padrões não lineares e revelar um bom desempenho em estudos de contexto financeiro.
Acresce o facto deste modelo possuir ferramentas para reduzir o impacto do ruídos dos dados, uma vez que ele é capaz de se ajustar e focar-se em padrões consistentes, melhorando assim a sua generalização.
Referências:\
https://www.sciencedirect.com/science/article/pii/S1062940824001669
https://www.kaggle.com/code/meuge672/predicting-price-with-adaboost-and-regression#AdaBoost-Algorithm
# Treinar o AdaBoost
adaboost_model = AdaBoostRegressor(n_estimators=20, learning_rate=0.001, loss='exponential', random_state=42)
# Treinar o modelo com o conjunto de treino
adaboost_model.fit(X_train_normalized, y_train)
# Avaliar o modelo no conjunto de validação
y_val_pred = adaboost_model.predict(X_val_normalized)
val_mae = mean_absolute_error(y_val, y_val_pred)
val_mse = mean_squared_error(y_val, y_val_pred)
val_rmse = np.sqrt(val_mse)
val_r2 = r2(y_val, y_val_pred)
print(f"Validação - MAE: {val_mae:.5f}")
print(f"Validação - MSE: {val_mse:.5f}")
print(f"Validação - RMSE: {val_rmse:.5f}")
print(f"Validação - R²: {val_r2:.5f}")
Validação - MAE: 0.01204 Validação - MSE: 0.00031 Validação - RMSE: 0.01756 Validação - R²: 0.07003
# Treinar no conjunto combinado (treino + validação)
X_train_val_selected = pd.concat([X_train_normalized, X_val_normalized])
y_train_val = pd.concat([y_train, y_val])
adaboost_model.fit(X_train_val_selected, y_train_val)
AdaBoostRegressor(learning_rate=0.001, loss='exponential', n_estimators=20,
random_state=42)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
AdaBoostRegressor(learning_rate=0.001, loss='exponential', n_estimators=20,
random_state=42)# Avaliar o conjunto de teste
y_test_pred = adaboost_model.predict(X_test_normalized)
test_mae = mean_absolute_error(y_test, y_test_pred)
test_mse = mean_squared_error(y_test, y_test_pred)
test_rmse = np.sqrt(test_mse)
test_r2 = r2(y_test, y_test_pred)
print(f"Teste - MAE: {test_mae:.5f}")
print(f"Teste - MSE: {test_mse:.5f}")
print(f"Teste - RMSE: {test_rmse:.5f}")
print(f"Teste - R²: {test_r2:.5f}")
Teste - MAE: 0.01139 Teste - MSE: 0.00028 Teste - RMSE: 0.01676 Teste - R²: 0.03132
Plots dos Valores Reais e dos Valores Previstos por Empresa¶
# Criar o DataFrame com os valores reais e previstos
y_test_df = pd.DataFrame(y_test.values, columns=['Real_Return'], index=y_test.index)
y_test_pred_df = pd.DataFrame(y_test_pred, columns=['Predicted_Return'], index=y_test.index)
# Concatenar os valores reais, previstos e os Tickers
df_test = pd.concat([y_test_df, y_test_pred_df], axis=1)
df_test['Ticker'] = X_test['Ticker']
# Gráficos por empresa
tickers = df_test['Ticker'].unique()
n_col = 5 # Número de gráficos por linha
n_row = int(np.ceil(len(tickers) / n_col)) # Número de linhas necessárias
print("Linha azul --- Valores Reais")
print("Linha laranja --- Valores Previstos")
# Criar os subgráficos
fig, axes = plt.subplots(n_row, n_col, figsize=(18, 3 * n_row))
axes = axes.flatten()
# Iterar sobre os tickers e plotar os gráficos
for i, ticker in enumerate(tickers):
# Filtrar os dados para o Ticker específico
ticker_data = df_test[df_test['Ticker'] == ticker]
# Plotar os valores reais e previstos ao longo do tempo
ax = axes[i]
ax.plot(ticker_data.index, ticker_data['Real_Return'], label='Valores Reais', color='blue')
ax.plot(ticker_data.index, ticker_data['Predicted_Return'], label='Previsões', color='orange', linestyle='dashed')
ax.set_title(f'Previsão de Retornos para {ticker}', fontsize=6)
ax.set_xlabel('Data', fontsize=6)
ax.set_ylabel('Retorno Futuro', fontsize=6)
ax.tick_params(axis='x', rotation=45, labelsize=6)
ax.tick_params(axis='y', labelsize=6)
plt.subplots_adjust(hspace=0.5, wspace=0.5, right=0.85)
plt.tight_layout(pad=3.0)
plt.show()
Linha azul --- Valores Reais Linha laranja --- Valores Previstos